import { Injectable } from '@nestjs/common';
import { ROOT_NODE_ID } from 'src/common/constants/menu';
import { MenuEnum, StatusEnum } from 'src/common/enum/menu';
import { PrismaService } from 'src/prisma/prisma.service';
import { MenuAndRolesType, MenuItem, MenuTreeItem } from 'src/type/menu';
import { convertToCamelCase, transformToOption } from 'src/utils';
import { CreateMenuDto } from './dto/create.menu.dto';
import { FindAllMenuDto } from './dto/find-all-menu.dto';
import { UpdateMenuDto } from './dto/update.menu.dto';

@Injectable()
export class MenuService {
  constructor(private prisma: PrismaService) {}

  async findForm(id: number): Promise<UpdateMenuDto> {
    const menu = await this.prisma.menu.findUnique({
      where: {
        id,
      },
      select: {
        id: true,
        name: true,
        type: true,
        path: true,
        component: true,
        icon: true,
        parentId: true,
        sort: true,
        perm: true,
        redirect: true,
        alwaysShow: true,
        visible: true,
        keepAlive: true,
      },
    });
    return {
      ...menu,
      type: MenuEnum[menu.type],
    };
  }

  delete(id: number) {
    return this.prisma.menu.delete({
      where: {
        id,
      },
    });
  }

  /**
   * 更新菜单信息
   * @param id 菜单的唯一标识符
   * @param body 包含更新内容的对象，其中必须包含type属性
   * @returns 返回更新后的菜单信息
   */
  update(id: number, body: UpdateMenuDto) {
    // 使用Prisma客户端更新菜单信息，其中type属性会根据传入的值转换为枚举类型
    return this.prisma.menu.update({
      where: {
        id,
      },
      data: {
        ...body,
        type: MenuEnum[body.type], // 将传入的type字符串转换为枚举类型
      },
    });
  }

  /**
   * 创建一个新的菜单项。
   * @param body 包含菜单项创建信息的DTO（数据传输对象），应包括菜单的类型和其他必要信息。
   * @returns 返回通过Prisma ORM创建的菜单项实例。
   */
  create(body: CreateMenuDto) {
    // 使用Prisma客户端创建一个新的菜单项，将请求体中的数据和菜单类型（通过枚举获取）一并传递
    return this.prisma.menu.create({
      data: {
        ...body,
        type: MenuEnum[body.type], // 将传入的类型名称转换为枚举值
      },
    });
  }

  /**
   * 异步获取菜单树结构。
   * @param query 包含查找条件的对象，其中keywords为关键字，用于过滤菜单名中包含此关键字的菜单项。
   * @returns 返回一个Promise，解析为根据指定条件生成的菜单树结构。
   */
  async findMenusTree(query: FindAllMenuDto) {
    // 使用Prisma客户端查询满足条件的菜单项，选择需要的字段
    const menus = await this.prisma.menu.findMany({
      where: {
        name: {
          contains: query.keywords, // 按照关键字查询菜单名
        },
      },
      select: {
        id: true,
        name: true,
        parentId: true,
        type: true,
        path: true,
        component: true,
        sort: true,
        visible: true,
        icon: true,
        redirect: true,
        perm: true,
      },
    });

    // 调用generateTree方法，将查询到的菜单列表转换为树结构，假设根节点的parentId为null
    return this.generateTree(menus, ROOT_NODE_ID);
  }

  /**
   * 生成菜单树结构。
   * @param menus 菜单列表，每个菜单项包含 parentId 和 id 等属性。
   * @param parentId 当前遍历层级的父菜单ID。
   * @returns 返回一个菜单树结构数组，每个菜单项包括完整的菜单信息、类型以及其子菜单。
   */
  private generateTree(menus: MenuItem[], parentId: number): MenuTreeItem[] {
    // 筛选出父菜单ID与指定ID匹配的菜单项
    return (
      menus
        .filter((menu) => {
          return menu.parentId === parentId;
        })
        // 将每个菜单项转换成树结构形式，并递归生成其子菜单
        .map((menu) => {
          return {
            ...menu,
            type: MenuEnum[menu.type], // 将菜单项的类型转换为枚举形式
            children: this.generateTree(menus, menu.id), // 递归生成该菜单项的子菜单
          };
        })
    );
  }

  /**
   * 该函数异步从数据库检索根路由，然后根据检索到的数据构建路由。
   * @returns `listRoutes` 函数返回调用 `this.buildRoutes(ROOT_NODE_ID, rootRoutes)` 的结果。`rootRoutes` 变量是通过使用
   * Prisma 查询数据库来查找许多菜单项及其相关角色而获得的。然后，该函数根据检索到的数据构建路线并返回结果。
   */
  async listRoutes() {
    const rootRoutes = await this.prisma.menu.findMany({
      include: {
        roles: {
          select: {
            role: {
              select: {
                code: true,
              },
            },
          },
        },
      },
    });
    return this.buildRoutes(ROOT_NODE_ID, rootRoutes);
  }

  /**
   * 函数 buildRoutes 过滤并映射菜单列表，以根据父子关系构建嵌套的路线对象，并为每个路线项提供特定的配置。
   * @param {number} parentId - `buildRoutes` 函数中的 `parentId` 参数用于根据菜单项的父 ID 筛选菜单项。它有助于为给定的父菜单项构建嵌套路由。
   * @param {MenuAndRolesType} menuList - MenuAndRolesType 是一个对象数组，其结构如下：
   * @returns `buildRoutes` 函数根据提供的 `menuList` 返回路由对象数组。该函数过滤 `menuList` 以查找具有特定 `parentId` 且类型不等于
   * 4（删除按钮）的项目。然后，它映射过滤后的项目以创建具有组件、名称、路径、重定向、子项和元等属性的路由对象。
   */
  buildRoutes(parentId: number, menuList: MenuAndRolesType) {
    //  item.type !== 4 把按钮去掉
    return menuList
      .filter((item) => item.parentId === parentId && item.type !== 4)
      .map((item) => {
        const children = this.buildRoutes(item.id, menuList);

        const meta = {
          title: item.name,
          icon: item.icon,
          hidden: item.visible === StatusEnum.DISABLE,
          // 【菜单】是否开启页面缓存,
          // 如果是菜单 MenuEnum.MENU === item.type
          keepAlive: MenuEnum.MENU === item.type && item.keepAlive === 1 ? true : undefined,
          // 【目录】只有一个子路由是否始终显示
          alwaysShow: MenuEnum.CATALOG === item.type && item.alwaysShow === 1 ? true : undefined,
          roles: item.roles.map((item) => item.role.code),
        };

        return {
          component: item.component,
          name: convertToCamelCase(item.path),
          path: item.path,
          redirect: item.redirect || undefined,
          children: children.length ? children : undefined,
          meta,
        };
      });
  }

  /**
   * 函数“findOptions”使用 Prisma 从数据库异步检索菜单数据，并根据检索到的数据生成树结构。
   * @returns `findOptions` 函数返回使用 `menus` 数组和 `ROOT_NODE_ID` 常量作为参数调用 `transformToOption`
   * 函数的结果。`transformToOption` 函数可能根据 `menus` 数组中提供的菜单项构建树结构。
   */
  async findOptions() {
    const menus = await this.prisma.menu.findMany({
      select: {
        id: true,
        name: true,
        parentId: true,
      },
    });
    return transformToOption(menus);
  }
}
